package cromosomas;

import java.util.ArrayList;
import java.util.Random;

/**
 * class Geneticos
 * 
 * @author Marco Gallardo Casu, Miguel Cisneros Rojas
 * 
 */
public class Geneticos {

	private int poblacion;
	private double cruce;
	private double mutacion;
	private double tolerancia;
	private int funcion;
	private int variables;
	private boolean esTorneo;

	private ArrayList<Cromosoma> cromosomas;

	// Ruleta
	private static int n; // N a reproducir
	private static int x; // X para mantener (elite)
	// Torneo
	private static int k; // Iteraciones del torneo
	private static int z = 3; // Z elementos para minitorneo
	// Aptitud global
	private static double aptitudTotal;

	public Geneticos(int poblacion, double cruce, double mutacion,
			double tolerancia, int funcion, int variables, boolean esTorneo) {
		this.poblacion = poblacion;
		this.cruce = cruce;
		this.mutacion = mutacion;
		this.tolerancia = tolerancia;
		this.funcion = funcion;
		this.variables = variables;
		this.esTorneo = esTorneo;
		this.cromosomas = generarPoblacionAlAzar();
		Geneticos.x = poblacion / 20;
		Geneticos.n = poblacion - x;
		Geneticos.k = poblacion - x;
	}

	private ArrayList<Cromosoma> generarPoblacionAlAzar() {
		ArrayList<Cromosoma> lista = new ArrayList<Cromosoma>(poblacion);
		for (int i = 0; i < poblacion; i++) {
			switch (funcion) {
			case 1:
				agregar(lista, new CromosomaF1(tolerancia));
				break;
			case 2:
				agregar(lista, new CromosomaF2(tolerancia));
				break;
			case 3:
				agregar(lista, new CromosomaF3(tolerancia));
				break;
			case 4:
				agregar(lista, new CromosomaF4(variables, tolerancia));
				break;
			case 5:
				agregar(lista, new CromosomaF5(tolerancia));
				break;
			case 6:
				agregar(lista, new CromosomaEJ1(tolerancia));
				break;
			}
		}
		return lista;
	}

	public Cromosoma getMejor() {
		return cromosomas.get(0);
	}

	public double getAptitudMejor() {
		return cromosomas.get(0).getAptitud();
	}

	public double getMediaGen() {
		double media = 0;

		for (Cromosoma c : cromosomas)
			media += c.getAptitud();

		media /= cromosomas.size();
		return media;
	}

	public void ciclo() {
		actualizarProbabilidades();
		ArrayList<Cromosoma> lista = seleccion();
		reproduccion(lista);
	}

	private void reproduccion(ArrayList<Cromosoma> lista) {
		boolean[] mask = new boolean[lista.size()];
		int i = 0;
		while (i < lista.size() - 1) {
			int index1 = (new Random()).nextInt(lista.size());
			while (!mask[index1]) {
				int index2 = (new Random()).nextInt(lista.size());
				if (index1 != index2 && !mask[index2]) {
					// Marcar escogidos
					mask[index1] = mask[index2] = true;
					// Coger los escogidos
					Cromosoma child1 = lista.get(index1);
					Cromosoma child2 = lista.get(index2);
					// Restar las aptitudes al total
					aptitudTotal -= child1.getAptitud();
					aptitudTotal -= child2.getAptitud();
					// Cruzan
					cruce(child1, child2);
					// Mutan
					mutar(child1);
					mutar(child2);
					agregar(cromosomas, child1);
					agregar(cromosomas, child2);
					i += 2;
				}
			}
		}
		if (lista.size() % 2 == 1) {
			boolean encontrado = false;
			for (int index = 0; !encontrado && index < lista.size(); index++) {
				encontrado = !mask[index];
				if (encontrado) {
					agregar(cromosomas, lista.get(index));
				}
			}
		}
	}

	private ArrayList<Cromosoma> seleccion() {
		ArrayList<Cromosoma> lista;
		if (esTorneo) {
			lista = seleccionTorneo();
		} else {
			lista = seleccionRuleta();
		}
		return lista;
	}

	private ArrayList<Cromosoma> seleccionRuleta() {
		ArrayList<Cromosoma> lista = new ArrayList<Cromosoma>(n);
		int k = 0;
		double pX = x > 0 ? cromosomas.get(x - 1).getpAcumulada() : 0.0;
		while (k < n) {
			double p = (new Random()).nextDouble();
			if (p <= pX) {
				continue;
			}
			int index = busquedaBin2(cromosomas, p);
			if (index == cromosomas.size()) {
				index--;
			}
			agregar(lista, cromosomas.remove(index));
			k++;
		}
		return lista;
	}

	private ArrayList<Cromosoma> seleccionTorneo() {
		ArrayList<Cromosoma> lista = new ArrayList<Cromosoma>(k);
		// final int LENGTH = cromosomas.size();
		for (int repeticion = 0; repeticion < k; repeticion++) {
			ArrayList<Cromosoma> torneo = new ArrayList<Cromosoma>(z);
			int i = 0;
			while (i < z && cromosomas.size() > x) {
				int index = (new Random()).nextInt(cromosomas.size());
				if (x <= index) {
					agregar(torneo, cromosomas.get(index));
					cromosomas.remove(index);
					i++;
				}
			}
			agregar(lista, torneo.get(0));
			torneo.remove(0);
			for (Cromosoma c : torneo) {
				agregar(cromosomas, c);
			}
		}
		return lista;
	}

	private void cruce(Cromosoma c1, Cromosoma c2) {
		double p = (new Random()).nextDouble();
		if (p < cruce) {
			int puntoCruce = (new Random()).nextInt(c1.getLongitud());
			boolean[] aux = c1.getGenes().clone();
			for (int i = puntoCruce; i < c1.getLongitud(); i++) {
				c1.setGen(i, c2.getGen(i));
				c2.setGen(i, aux[i]);
			}
		}
	}

	private void mutar(Cromosoma c) {
		for (int i = 0; i < c.getLongitud(); i++) {
			double p = (new Random()).nextDouble();
			c.setGen(i, (p < mutacion) ? !c.getGen(i) : c.getGen(i));
		}
	}

	// Auxiliares.

	private int busquedaBin(ArrayList<Cromosoma> lista, double aptitud) {
		return busquedaBin(lista, 0, lista.size() - 1, aptitud);
	}

	private int busquedaBin(ArrayList<Cromosoma> lista, int c, int f,
			double aptitud) {
		if (c > f) {
			return c;
		}
		int medio = (c + f) / 2;
		double aptitudMedio = lista.get(medio).getAptitud();
		if (aptitudMedio == aptitud) {
			return medio;
		} else if (aptitudMedio > aptitud) {
			return busquedaBin(lista, medio + 1, f, aptitud);
		} else {
			return busquedaBin(lista, c, medio - 1, aptitud);
		}
	}

	private int busquedaBin2(ArrayList<Cromosoma> lista, double p) {
		return busquedaBin2(lista, 0, lista.size() - 1, p);
	}

	private int busquedaBin2(ArrayList<Cromosoma> lista, int c, int f, double p) {
		if (c > f) {
			return c;
		}
		int medio = (c + f) / 2;
		double pMedio = lista.get(medio).getpAcumulada();
		if (pMedio == p) {
			return medio;
		} else if (pMedio > p) {
			return busquedaBin2(lista, c, medio - 1, p);
		} else {
			return busquedaBin2(lista, medio + 1, f, p);
		}
	}

	private void agregar(ArrayList<Cromosoma> lista, Cromosoma nuevo) {
		nuevo.evalua();
		aptitudTotal += nuevo.getAptitud();
		if (lista.isEmpty()) {
			lista.add(nuevo);
		} else {
			lista.add(busquedaBin(lista, nuevo.getAptitud()), nuevo);
		}
	}

	private void actualizarProbabilidades() {
		Cromosoma c = cromosomas.get(0);
		c.setpSeleccion(c.getAptitud() / aptitudTotal);
		c.setpAcumulada(c.getpSeleccion());
		for (int i = 1; i < cromosomas.size() - 1; i++) {
			c = cromosomas.get(i);
			double pSeleccion = c.getAptitud() / aptitudTotal;
			c.setpSeleccion(pSeleccion);
			c.setpAcumulada(pSeleccion + cromosomas.get(i - 1).getpAcumulada());
		}
		c = cromosomas.get(cromosomas.size() - 1);
		c.setpSeleccion(c.getAptitud() / aptitudTotal);
		c.setpAcumulada(1.0);
	}
}
